import * as React from 'react';
import * as ReactDOM from "react-dom";
import * as classNames from 'classnames';
import offset from "dom-helpers/query/offset";
import warning from 'warning';

export function traverseTreeNodes(treeNodes, callback) {
  const traverse = (subTreeNodes, level, parentsChildrenPos, parentPos?) => {
    if (Array.isArray(subTreeNodes)) {
      subTreeNodes = subTreeNodes.filter(item => !!item);
    }
    React.Children.forEach(subTreeNodes, (item, index) => {
      const pos = `${level}-${index}`;
      parentsChildrenPos.push(pos); // Note: side effect

      const childrenPos = [];


      const item1 = item as any;
      if (item1.props.children && item1.type && item1.type.isTreeNode) {
        traverse(item1.props.children, pos, childrenPos, pos);
      }
      callback(
        item,
        index,
        pos,
        item1.key || pos,
        childrenPos,
        parentPos
      );
    });
  };
  traverse(treeNodes, 0, []);
}

export function isInclude(smallArray, bigArray) {
  return smallArray.every((item, index) => {
    return item === bigArray[index];
  });
}
function noop() {
}

export class Tree extends React.PureComponent<any, any> {
  static propTypes = {
    prefixCls: React.PropTypes.string,
    children: React.PropTypes.any,
    showLine: React.PropTypes.bool,
    showIcon: React.PropTypes.bool,
    selectable: React.PropTypes.bool,
    multiple: React.PropTypes.bool,
    checkable: React.PropTypes.oneOfType([
      React.PropTypes.bool,
      React.PropTypes.node,
    ]),
    _treeNodesStates: React.PropTypes.object,
    checkStrictly: React.PropTypes.bool,
    draggable: React.PropTypes.bool,
    autoExpandParent: React.PropTypes.bool,
    defaultExpandAll: React.PropTypes.bool,
    defaultExpandedKeys: React.PropTypes.arrayOf(React.PropTypes.string),
    expandedKeys: React.PropTypes.arrayOf(React.PropTypes.string),
    defaultCheckedKeys: React.PropTypes.arrayOf(React.PropTypes.string),
    checkedKeys: React.PropTypes.oneOfType([
      React.PropTypes.arrayOf(React.PropTypes.string),
      React.PropTypes.object,
    ]),
    defaultSelectedKeys: React.PropTypes.arrayOf(React.PropTypes.string),
    selectedKeys: React.PropTypes.arrayOf(React.PropTypes.string),
    onExpand: React.PropTypes.func,
    onCheck: React.PropTypes.func,
    onSelect: React.PropTypes.func,
    loadData: React.PropTypes.func,
    onMouseEnter: React.PropTypes.func,
    onMouseLeave: React.PropTypes.func,
    onRightClick: React.PropTypes.func,
    onDragStart: React.PropTypes.func,
    onDragEnter: React.PropTypes.func,
    onDragOver: React.PropTypes.func,
    onDragLeave: React.PropTypes.func,
    onDrop: React.PropTypes.func,
    filterTreeNode: React.PropTypes.func,
    openTransitionName: React.PropTypes.string,
    openAnimation: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.object]),
  }

  static defaultProps = {
    prefixCls: 'rc-tree',
    showLine: false,
    showIcon: true,
    selectable: true,
    multiple: false,
    checkable: true,
    showChecked: false,
    draggable: false,
    defaultCheckedKeys: [],
    defaultSelectedKeys: [],
    onExpand: noop,
    onCheck: noop,
    onSelect: noop,
    onDragStart: noop,
    onDragEnter: noop,
    onDragLeave: noop,
    onDrop: noop,
  }

  checkedNodes = [];

  constructor(props) {
    super(props)
    this.state = {
      expandedKeys: [],
      checkedKeys: [],
      selectedKeys: [],
      dragNodesKeys: '',
      dragOverNodeKey: '',
      dropNodeKey: '',
      contextmenuKeys: [],
      checkedKeysChange: true,
    };
  }

  componentWillReceiveProps(nextProps) {
    // const checkedKeys = this.getDefaultCheckedKeys(nextProps, true);
    // const selectedKeys = this.getDefaultSelectedKeys(nextProps, true);
    // const st = {};
    // if (expandedKeys) {
    //   st.expandedKeys = expandedKeys;
    // }
    // if (checkedKeys) {
    //   if (nextProps.checkedKeys === this.props.checkedKeys) {
    //     this.checkedKeysChange = false;
    //   } else {
    //     this.checkedKeysChange = true;
    //   }
    //   st.checkedKeys = checkedKeys;
    // }
    // if (selectedKeys) {
    //   st.selectedKeys = selectedKeys;
    // }
    // this.setState(st);
  }

  private dragNode;
  private dragNodesKeys;
  private expandedKeys;
  private dropPosition;
  private delayedDragEnterLogic;

  onDragStart(e, treeNode) {
    this.dragNode = treeNode;
    this.dragNodesKeys = this.getDragNodes(treeNode);
    const st = {
      dragNodesKeys: this.dragNodesKeys,
      expandedKeys: undefined
    };
    const expandedKeys = this.getExpandedKeys(treeNode, false);
    if (expandedKeys) {
      // Controlled expand, save and then reset
      // this.getRawExpandedKeys();
      st.expandedKeys = expandedKeys;
    }
    this.setState(st);
    this.props.onDragStart({
      event: e,
      node: treeNode,
    });
  }

  calcDropPosition(e, treeNode) {
    const offsetTop = offset(treeNode.selectHandle).top;
    const offsetHeight = treeNode.selectHandle.offsetHeight;
    const pageY = e.pageY;
    const gapHeight = 2; // TODO: remove hard code
    if (pageY > offsetTop + offsetHeight - gapHeight) {
      return 1;
    }
    if (pageY < offsetTop + gapHeight) {
      return -1;
    }
    return 0;
  }

  onDragEnter(e, treeNode) {
    const dropPosition = this.calcDropPosition(e, treeNode);
    if (this.dragNode.props.eventKey === treeNode.props.eventKey &&
      dropPosition === 0) {
      this.setState({
        dragOverNodeKey: '',
      });
      return;
    }
    this.setState({
      dragOverNodeKey: treeNode.props.eventKey,
      dropPosition,
    });

    if (!this.delayedDragEnterLogic) {
      this.delayedDragEnterLogic = {};
    }
    Object.keys(this.delayedDragEnterLogic).forEach((key) => {
      clearTimeout(this.delayedDragEnterLogic[key]);
    });
    this.delayedDragEnterLogic[treeNode.props.pos] = setTimeout(() => {
      const expandedKeys = this.getExpandedKeys(treeNode, true);
      if (expandedKeys) {
        this.setState({ expandedKeys });
      }
      this.props.onDragEnter({
        event: e,
        node: treeNode,
        expandedKeys: expandedKeys && [...expandedKeys] || [...this.state.expandedKeys],
      });
    }, 400);
  }

  onDragOver(e, treeNode) {
    this.props.onDragOver({ event: e, node: treeNode });
  }

  onDragLeave(e, treeNode) {
    this.props.onDragLeave({
      event: e,
      node: treeNode
    });
  }
  onDrop(e, treeNode) {
    const { state } = this;
    const eventKey = treeNode.props.eventKey;
    this.setState({
      dragOverNodeKey: '',
      dropNodeKey: eventKey,
    });
    if (state.dragNodesKeys.indexOf(eventKey) > -1) {
      warning(false, 'Can not drop to dragNode(include it\'s children node)');
      return;
    }

    const posArr = treeNode.props.pos.split('-');
    const res = {
      event: e,
      node: treeNode,
      dragNode: this.dragNode,
      dragNodesKeys: [...state.dragNodesKeys],
      dropPosition: state.dropPosition + Number(posArr[posArr.length - 1]),
      dropToGap: undefined
    };
    if (state.dropPosition !== 0) {
      res.dropToGap = true;
    }
    this.props.onDrop(res);
  }

  onSelect(treeNode) {
    const { props, state } = this;
    const eventKey = treeNode.props.eventKey;
    const selected = !treeNode.props.selected;

    let selectedKeys = [...state.selectedKeys];
    if (!selected) {
      const index = selectedKeys.indexOf(eventKey);
      selectedKeys.splice(index, 1);
    } else if (!props.multiple) {
      selectedKeys = [eventKey];
    } else {
      selectedKeys.push(eventKey);
    }

    // TODO: can be optimized if we remove selectedNodes in API
    const selectedNodes = [];
    if (selectedKeys.length) {
      traverseTreeNodes(props.children, (item) => {
        if (selectedKeys.indexOf(item.key) !== -1) {
          selectedNodes.push(item);
        }
      });
    }

    if (!('selectedKeys' in props)) {
      this.setState({
        selectedKeys,
      });
    }

    const eventObj = {
      event: 'select',
      selected,
      node: treeNode,
      selectedNodes,
    };
    props.onSelect(selectedKeys, eventObj);
  }

  onMouseEnter(e, treeNode) {
    this.props.onMouseEnter({
      event: e,
      node: treeNode
    });
  }

  onMouseLeave(e, treeNode) {
    this.props.onMouseLeave({
      event: e,
      node: treeNode
    });
  }

  onContextMenu(e, treeNode) {
    this.props.onRightClick({ event: e, node: treeNode });
  }

  // all keyboard events callbacks run from here at first
  onKeyDown(e) {
    e.preventDefault();
  }

  getOpenTransitionName() {
    const props = this.props;
    let transitionName = props.openTransitionName;
    const animationName = props.openAnimation;
    if (!transitionName && typeof animationName === 'string') {
      transitionName = `${props.prefixCls}-open-${animationName}`;
    }
    return transitionName;
  }

  getDragNodes(treeNode) {
    const dragNodesKeys = [];
    const treeNodePosArr = treeNode.props.pos.split('-');
    traverseTreeNodes(treeNode.props.children, (item, index, pos, key) => {
      const childPosArr = pos.split('-');
      if (
        treeNode.props.pos === pos ||
        treeNodePosArr.length < childPosArr.length && isInclude(treeNodePosArr, childPosArr)
      ) {
        dragNodesKeys.push(key);
      }
    });
    dragNodesKeys.push(treeNode.props.eventKey || treeNode.props.pos);
    return dragNodesKeys;
  }

  getExpandedKeys(treeNode, expand) {
    const key = treeNode.props.eventKey;
    const expandedKeys = this.state.expandedKeys;
    const expandedIndex = expandedKeys.indexOf(key);
    let exKeys;
    if (expandedIndex > -1 && !expand) {
      exKeys = [...expandedKeys];
      exKeys.splice(expandedIndex, 1);
      return exKeys;
    }
    if (expand && expandedKeys.indexOf(key) === -1) {
      return expandedKeys.concat([key]);
    }
  }
  filterTreeNode(treeNode) {
    const filterTreeNode = this.props.filterTreeNode;
    if (typeof filterTreeNode !== 'function' || treeNode.props.disabled) {
      return false;
    }
    return filterTreeNode.call(this, treeNode);
  }

  checkedNode(nodeKey) {
    var checkedEvent = new CustomEvent("checkedNode", {
      detail: { nodeKey }
    });
    ReactDOM.findDOMNode(this).dispatchEvent(checkedEvent);
  }
  expandedNode(nodeKey) {
    var checkedEvent = new CustomEvent("expandedNode", {
      detail: { nodeKey }
    });
    ReactDOM.findDOMNode(this).dispatchEvent(checkedEvent);
  }
  addCheckedNode(treeNode) {
    this.checkedNodes.push(treeNode);
  }
  removeCheckedNode(treeNode) {
    const index = this.checkedNodes.indexOf(treeNode);
    if (index !== -1) {
      this.checkedNodes.splice(index, 1);
    }
  }

  renderTreeNode(child, index, level = 0, parent?) {
    const pos = `${level}-${index}`;
    const key = child.key || pos;
    const state = this.state;
    const props = this.props;
    const cloneProps = {
      ref: 'treeNode-' + key,
      root: this,
      parent: parent,
      eventKey: key,
      selectable: props.selectable,
      onMouseEnter: props.onMouseEnter,
      onMouseLeave: props.onMouseLeave,
      onRightClick: props.onRightClick,
      onCheck: props.onCheck,
      onExpand: props.onExpand,
      prefixCls: props.prefixCls,
      showLine: props.showLine,
      showIcon: props.showIcon,
      draggable: props.draggable,
      showChecked: props.multiple || props.showChecked,
      checkable: props.checkable,
      multiple: props.multiple,
      iconClass: child.props.iconClass || props.iconClass,
      dragOver: state.dragOverNodeKey === key && this.dropPosition === 0,
      dragOverGapTop: state.dragOverNodeKey === key && this.dropPosition === -1,
      dragOverGapBottom: state.dragOverNodeKey === key && this.dropPosition === 1,
      openTransitionName: this.getOpenTransitionName(),
    };

    return React.cloneElement(child, cloneProps);
  }
  render() {
    const props = this.props;
    const domProps = {
      className: classNames("tree", props.className),
      role: 'tree-node',
      tabIndex: undefined,
      onKeyDown: undefined
    };
    if (props.focusable) {
      domProps.tabIndex = '0';
      domProps.onKeyDown = this.onKeyDown;
    }
    // console.log(this.state.expandedKeys, this._rawExpandedKeys, props.children);
    return (
      <div {...domProps} unselectable ref="tree"> {
        React.Children.map(props.children, this.renderTreeNode)
      } </div>
    );
  }
}


export class TreeNode extends React.PureComponent<any, any>{
  static defaultProps = {
    onExpand: noop,
    unSelectable: false,
    multiSelectable: false,

  }

  constructor(props) {
    super(props)
    this.state = {
      expanded: this.props.expanded || false,
      checked: false,
      dragging: false,
    };
  }

  onCheckedNode(event) {
    if (event.detail.nodeKey == this.props.eventKey) {
      this.check(true);
    }
    event.stopPropagation();
  }

  onExpandNode(event) {
    if (event.detail.nodeKey == this.props.eventKey) {
      this.expand(true);
    }
    event.stopPropagation();
  }
  componentDidMount() {
    ReactDOM.findDOMNode(this.props.root).addEventListener("checkedNode", this.onCheckedNode);
    ReactDOM.findDOMNode(this.props.root).addEventListener("expandedNode", this.onExpandNode);
  }
  componentWillUnmount() {
    ReactDOM.findDOMNode(this.props.root).removeEventListener("checkedNode", this.onCheckedNode);
    ReactDOM.findDOMNode(this.props.root).removeEventListener("expandedNode", this.onExpandNode);
  }

  onCheck(event) {
    this.props.disabled || this.check(!this.state.checked);
    event.stopPropagation();
  }
  onSelect() {
    this.props.root.onSelect(this);
  }
  onMouseEnter(e) {
    e.preventDefault();
    this.props.root.onMouseEnter(e, this);
  }
  onMouseLeave(e) {
    e.preventDefault();
    this.props.root.onMouseLeave(e, this);
  }
  onContextMenu(e) {
    e.preventDefault();
    this.props.root.onContextMenu(e, this);
  }
  onDragStart(e) {
    console.log('dragstart', this.props.eventKey);
    e.dataTransfer.setData("eventKey", this.props.eventKey);
    e.stopPropagation();
    this.setState({
      dragging: true
    })

    //window.draggedEventKey = this.props.eventKey;
    // this.setState({
    //     dragNodeHighlight: true,
    // });
    //this.props.root.onDragStart(e, this);       
    //e.dataTransfer.effectAllowed = 'all';
    //e.dataTransfer.setDragImage(ReactDOM.findDOMNode(this), 0, 0)

    try {
      // ie throw error
      e.dataTransfer.setData('text/plain', 'firefox-need-it');
    } finally {
      //empty
    }
  }
  onDragEnd(e) {
    e.dataTransfer.clearData("eventKey");
    this.setState({ dragging: false })
    //delete (window.draggedEventKey);
  }

  onDragEnter(e) {
    console.log('dragenter', this.props.eventKey);
    e.preventDefault();
    e.stopPropagation();

    if (!this.state.dragging) {
      this.expand(true);
    }
  }

  onDragOver(e) {
    this.props.root.onDragOver(e, this);
    e.stopPropagation();
  }

  onDragLeave(e) {
    e.stopPropagation();
    this.props.root.onDragLeave(e, this);
  }

  onDrop(e) {
    console.log("drop", this.props.eventKey)
    e.preventDefault();
    e.stopPropagation();
    this.setState({
      dragNodeHighlight: false,
    });
    this.props.root.onDrop(e, this);
  }

  onExpand(event) {
    this.expand(!this.state.expanded);
    event.stopPropagation();
  }
  handleClick(event) {
    if (!this.props.multiple) {
      this.expand(!this.state.expanded);
    }
    this.props.disabled || this.check(true);
  }
  expand(expanded) {
    this.props.onExpand();
    this.setState({ expanded })

    if (expanded && this.props.parent) {
      this.props.parent.expand(true);
    }
  }
  check(checked) {
    if (!this.props.multiple) {
      var oldCheckedNode = this.props.root.oldCheckedNode;
      if (checked) {
        if (oldCheckedNode && oldCheckedNode !== this) {
          oldCheckedNode.check(false);
        }
        this.props.root.oldCheckedNode = this;
      }
      else {
        if (oldCheckedNode && oldCheckedNode === this) {
          this.props.root.oldCheckedNode = null;
        }
      }
    }
    if (checked) {
      this.props.root.addCheckedNode(this);
    }
    else {
      this.props.root.removeCheckedNode(this);
    }

    if (this.props.parent) {
      this.props.parent.expand(true);
    }
    this.setState({ checked });
    this.props.onCheck();
  }
  // keyboard event support
  onKeyDown(e) {
    e.preventDefault();
  }

  renderSwitcher(props, expandedState) {
    const switcherCls = {
      'fa-folder': !expandedState,
      'fa-folder-open': expandedState,
    }
    return (<i className={classNames(switcherCls, 'fa')} onClick={this.onExpand}></i>);
  }
  renderCheckbox(props, checkedState) {
    if (!props.multiple && !props.showChecked) {
      return null;
    }
    const checkboxCls = {
      'fa-check': checkedState,
      'fa-square-o': !checkedState
    }
    //this.props.showCheckbox
    return (<i className={classNames(props.showChecked ? checkboxCls : {}, "fa tree-check")} onClick={this.onCheck} ></i>);
  }
  renderIcon(props) {
    return (<i style={{ marginLeft: '4px' }} className={props.iconClass} />)
  }
  renderChildren(props, expanded) {
    const children = props.children;
    let newChildren = null;
    let allTreeNode = false;
    if (Array.isArray(children)) {
      allTreeNode = children.every((item) => {
        return item.type === TreeNode;
      });
    }
    else if (children) {
      allTreeNode = children.type === TreeNode
    }

    if (allTreeNode) {
      const childrenStyle = {
        display: !expanded ? 'none' : ''
      }
      newChildren = (
        <div className={'tree-folder-content'} style={childrenStyle}>
          {
            React.Children.map(children, function (item, index) {
              return props.root.renderTreeNode(item, index, props.pos)
            })
          }
        </div>
      );
    }
    return newChildren;
  }
  render() {
    const props = this.props;
    const prefixCls = props.prefixCls;

    let hasChildren = true;
    let newChildren = this.renderChildren(props, this.state.expanded);
    if (!newChildren) {
      hasChildren = false;
    }

    // 如果默认不展开，不渲染进dom，在大量数据下，能使性能有很大提升！
    // if (!this.state.expanded) {
    //     newChildren = null;
    // }       

    const liProps = {};

    const headerCls = {
      'tree-selected': this.state.checked,
      "disabled": this.props.disabled
    }

    let headerProps = {};

    if (props.draggable) {
      headerProps = {
        draggable: true,
        onDragStart: this.onDragStart,
        onDragEnter: this.onDragEnter,
        onDragOver: this.onDragOver,
        onDrop: this.onDrop,
        onDragEnd: this.onDragEnd,
      }
    }
    if (hasChildren) {
      return (
        <div className={'tree-folder'}>
          <div onClick={this.handleClick} ref='header'
            className={classNames(headerCls, "tree-folder-header")}
            {...headerProps}>
            {this.renderSwitcher(props, this.state.expanded)}
            {props.checkable ? this.renderCheckbox(props, this.state.checked) : null}
            <div className="tree-folder-name">
              {props.title}
            </div>
          </div>
          {newChildren}
        </div>
      );
    }
    else {
      return (
        <div ref="item" onClick={this.handleClick}
          className={classNames(headerCls, 'tree-item')}
          {...headerProps}>
          {props.checkable ? this.renderCheckbox(props, this.state.checked) : null}
          <div className="tree-item-name">
            {this.renderIcon(props)}
            {props.title}
          </div>
        </div>
      );
    }
  }
}